package cn.hicard.auth.web;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import cn.hicard.common.Constant;
import cn.hicard.auth.entity.MgUserinfo;
import cn.hicard.auth.service.SourceService;
import cn.hicard.auth.service.UserinfoService;
import cn.hicard.auth.web.ctrl.UserinfoCtrl;
import cn.hicard.util.SpringContext;

/** 
 * 权限验证过滤器，配置过滤器时，最好将该过滤器配为最后一个。<br/>
 * <p>
 * <b>初始化参数：ignores</b>，忽略进行权限验证的路径集合，用逗号分割，如果路径是一个目录请以 / 结尾。<br/>
 * </p>
 * <p>
 * <b>工作模式：mode</b>，可选值有0，1，2，3。效果为：<br/>
 * 0-不验证<br/>
 * 1-纯URL访问验证<br/>
 * 2-纯HTML内容验证<br/>
 * 3-URL+HTML双重验证<br/>
 * </p>
 * <p>
 * <b>说明1：</b>使用URL验证，建议使用REST方式构架，这样效果就是最完美的<br/>
 * <b>说明2：</b>使用HTML验证，请尽量使用标准化HTML
 * </p>
 * <p>
 * <b>工作方式说明：</b><br/>
 * A.任意用户初次访问时，从数据库加载当前系统所有资源缓存入application<br/>
 * B.用户访问资源时，从数据库加载当前用户所拥有角色的所有资源组成的权限树并缓存入session<br/>
 * C.用户访问资源时，验证URL的层级，判断URL的路径是否存在于用户权限树中<br/>
 * D.当用户访问成功，准略返回HTML文本时，验证HTML层级，去掉相对于总权限树中用户无权访问的部分
 * </p>
 * @author zhangy
 *
 */
public class AuthFilter implements Filter {
	
	/** 系统权限全部总资源数据key：值存在于application */
	public static final String ROOT_AUTH_TREE = "root_auth_tree";
	/** 用户所拥有的权限角色的所有权限资源数据key：值存在于session */
	public static final String USER_AUTH_TREE = "user_auth_tree";
	/** 用户所拥有的菜单角色的所有菜单资源数据key：值存在于session */
	public static final String USER_MENU_TREE = "user_menu_tree";
	/** 已登录的用户所拥有的角色主键集合key：值存在于session */
	public static final String LOGON_ROLES = "logon_roles";
	/** 已登录的用户信息key：值存在于session */
	public static final String LOGON_USER = "logon_userinfo";
	/** 已登录的用户IDkey：值存在于session */
	public static final String LOGON_LOGINID = "logon_loginid";
	/** 系统权限：顶级根资源主键 */
	public static final int ROOT_SOURCE_PK = 1;
	/** 系统权限：顶级根菜单主键 */
	public static final int ROOT_MENU_PK = 2;

	
	/** 框架日志：frameLogger */
	private static Logger logger = Logger.getLogger(Constant.LOG_FRAME);
	
	private UserinfoService userinfoService;

	private SourceService sourceService;
	
	// 忽略验证的路径集合
	private List<String> ignores = new ArrayList<String>();
	// 验证模式
	private int authMode;
	// 网页编码：默认UTF-8
	private String encoding = "UTF-8";
	// 全局上下文
	private ServletContext application;
		

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 
			throws ServletException, IOException 
	{ 		
		// 初始化
		if(userinfoService == null){
			userinfoService = SpringContext.getBean(UserinfoService.class);
		}
		if(sourceService == null){
			sourceService = SpringContext.getBean(SourceService.class);
		}
		// 检查是否初始化成功，一般情况下数据库连接容易出错
		if(userinfoService==null || sourceService==null){
			request.setAttribute("message", "[权限模块]发生严重错误，权限初始化失败，很可是数据源连接出错！");
			request.getRequestDispatcher("/error.jsp").forward(request, response);
			return;
		}
		
		// 截获请求的URL
		HttpServletRequest req = (HttpServletRequest)request;
		StringBuffer url = req.getRequestURL();
		String contextPath = req.getContextPath();
		String path = url.substring(url.indexOf(contextPath));

		// 取得单点登录名的用户名 Remote User  // 
		String loginid = req.getRemoteUser();
		loginid = "sa";
		// 取得已登录的用户信息，然后取出该用户关联的用户角色集合，然后进行权限验证
		MgUserinfo user = (MgUserinfo)req.getSession().getAttribute(LOGON_USER);
		if(user == null){
			try {
				user = userinfoService.getByLoginid(loginid);
			} catch (Exception e) {
				logger.error("[权限模块]查找登录名为["+loginid+"]的用户信息失败！", e); 
			}
		}
		// 如果找不到用户信息就报错
		if(user == null){
			req.setAttribute("message", "[权限模块]用户名为" + loginid + "的用户信息不存在！该问题很可能是由于本系统用户数据未与单点登录服务器同步造成的！");
			request.getRequestDispatcher("/error.jsp").forward(request, response);
			return;
		}else{
			req.getSession().setAttribute(LOGON_USER, user);
			req.getSession().setAttribute(LOGON_LOGINID, loginid);
		}

		// 不论是否开启权限验证功能，普通用户(不是管理员)不允许进入权限管理页面
		if( UserinfoCtrl.UT_GENERAL_USER.equals(user.getUsertype()) && path.startsWith(contextPath+"/auth/")){
			req.setAttribute("message", "[权限模块]用户[" + loginid + "]为普通用户，无权访问权限管理页面！");
			request.getRequestDispatcher("/error.jsp").forward(request, response);
			return;			
		}
		
		
		// 用户的角色集合
		@SuppressWarnings("unchecked")
		List<Integer> roleids = (List<Integer>)req.getSession().getAttribute(LOGON_ROLES);
		if(roleids == null){
			try {
				roleids = userinfoService.findOwnRolePks(user.getPkUserinfo());
				req.getSession().setAttribute(LOGON_ROLES, roleids);
			} catch (Exception e) {
				logger.error("",e);
			}
		}


		// 总资源Tree，存入Application		
		Node allTree = (Node)application.getAttribute(ROOT_AUTH_TREE);
		if(allTree == null){
			try {
				// 从根开始，查询所有类型
				allTree = sourceService.findNodeByType(ROOT_SOURCE_PK, null);
				application.setAttribute(ROOT_AUTH_TREE, allTree);			
			} catch (Exception e) {
				logger.error("",e);
			}			
		}
		
		// 用户所拥有的角色的所有资源，存入Session
		Node userTree = (Node)req.getSession().getAttribute(USER_AUTH_TREE);
		if(userTree == null){
			try {
				userTree = sourceService.findNodeByRoles(ROOT_SOURCE_PK,roleids);
				req.getSession().setAttribute(USER_AUTH_TREE, userTree);
			} catch (Exception e) {
				logger.error("",e);
			}
		}
		
		// 用户所拥有的菜单角色的所有菜单，存入Session
		Node menuTree = (Node)req.getSession().getAttribute(USER_MENU_TREE);
		if(menuTree == null){
			try {
				menuTree = sourceService.findNodeByRoles(ROOT_MENU_PK,roleids);
				req.getSession().setAttribute(USER_MENU_TREE, menuTree);
			} catch (Exception e) {
				logger.error("",e);
			}
		}
		
		// ①未启用，直接通过
		if(authMode == 0){
			filterChain.doFilter(request, response);
			return;
		}		

		// ②跳过忽略验证的路径
		if(checkIgnore(path)){
			logger.debug("[权限模块]请求的路径被设置为忽略验证：" + path);
			filterChain.doFilter(request, response);
			return;
		}

		
		if(authMode == 1){
			// URL请求权限控制：
			if(Authentication.authPath(allTree, userTree, path) == false){
				request.setAttribute("message", "[权限模块]无访问权限");
				request.getRequestDispatcher("/error.jsp").forward(request, response);
			}else{
				filterChain.doFilter(request, response);
			}
		} else if(authMode == 2){
			authContent(request, response, filterChain, allTree, userTree);
		} else if(authMode == 3){
			// URL请求权限控制：
			if(Authentication.authPath(allTree, userTree, path) == false){
				request.setAttribute("message", "[权限模块]无访问权限");
				request.getRequestDispatcher("/error.jsp").forward(request, response);
			}else{
				authContent(request, response, filterChain, allTree, userTree);
			}				
				
		}
	}


	/**
	 * 访问的HTML内容验证
	 */
	private void authContent(ServletRequest request, ServletResponse response,
			FilterChain filterChain, Node allTree, Node userTree)
			throws IOException, ServletException {
		// 响应用户的资源权限控制：		
		AuthServletResponseWrapper wrapper = new AuthServletResponseWrapper((HttpServletResponse) response, encoding);	
		// 没有错误发生时才进行权限控制
		// 响应用户的资源权限控制：
		// 只对正常响应输出内容为HTML(text/html)的response进行权限内容过滤
		String contentType = response.getContentType();
		logger.debug("[权限模块]Html内容过滤开始，HttpStatus:" + wrapper.getHttpStatus() + ", contentType:"+contentType);
		if( wrapper.getHttpStatus()==200
			&& contentType != null
			&& contentType.trim().length()>="text/html".length()
			&& contentType.substring(0, 9).equalsIgnoreCase("text/html")
			)
		{	
			// 过滤处理
			filterChain.doFilter(request, wrapper);	
			String html = wrapper.getOutputText();
			html = Authentication.authHtml(allTree, userTree, html);
			response.setContentLength(-1);
			response.setCharacterEncoding(encoding);
			response.getWriter().write(html);
		}else{	
			// 原样处理
			filterChain.doFilter(request, response);	
			logger.debug("[权限模块]响应内容不符合过虑要求");
		}
	}

	
	/**
	 * 检查路径是否被设置为忽略权限验证
	 * @param path
	 * @return true忽略，false不忽略
	 */
	private boolean checkIgnore(String path){
		// 包含在忽略路径（通常是具体的文件）中
		if(ignores.contains(path)){
			return true;
		}
		// 包含在忽略目录下
		for (String dir : ignores) {
			if(dir.endsWith("/") && path.startsWith(dir)){
				return true;
			}
		}		
		return false;
	}
	
	
	/**
	 * 过滤器初始化
	 */
	public void init(FilterConfig config) throws ServletException {
		// WEB容器上下文
		application = config.getServletContext();
		
		//  读取Filter配置：网页编码
		String encodingConfig = config.getInitParameter("encoding");
		if(encodingConfig != null){
			this.encoding = encodingConfig.trim();
		}		
		// 读取Filter配置：忽略验证的路径
		String ignoreConfig = config.getInitParameter("ignores");
		if(ignoreConfig != null){
			String[] paths = ignoreConfig.split(",");
			for (int i = 0; i < paths.length; i++) {
				ignores.add(paths[i].trim());				
			}
		}
		//  读取Filter配置：确定验证模式
		String mode = config.getInitParameter("mode");
		if(mode != null){
			mode = mode.trim();
		}
		if("1".equals(mode)){
			authMode = 1;
			logger.info("[权限模块]权限验证模式：1-纯URL访问路径控制验证");
		} else if("2".equals(mode)){ 
			authMode = 2;
			logger.info("[权限模块]权限验证模式：2-纯HTML内容控制验证");
		} else if("3".equals(mode)){ 
			authMode = 3;
			logger.info("[权限模块]权限验证模式：3-(URL+HTML)双重控制验证");
		} else {
			authMode = 0;
			logger.info("[权限模块]权限验证模式：0-已关闭验证");
		}		
	}
	
	/**
	 * 过滤器销毁
	 */
	public void destroy() {
		
	}


}
